package top.byteio.common.core.crypto;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.List;

/**
 * 非对称加密算法RSA工具类<br>
 * 实现公钥/私钥的生成、编解码、加密解密功能，遵循"公钥加密私钥解密，私钥加密公钥解密"原则
 * <p>
 * 核心特性：
 * 1. 密钥编解码采用标准Base64格式（避免字节数组字符串化导致的解析错误）
 * 2. 明确指定加密模式和填充方式（RSA/ECB/PKCS1Padding），保证跨环境一致性
 * 3. 细化异常类型，便于调用方精准处理
 * 4. 限制密钥最小长度为2048位（符合安全规范）
 * </p>
 *
 * @author lerry
 * @since 2023/6/22 19:14
 */
@Slf4j
public class RSA implements ICrypt {

    //初始化Jackson的ObjectMapper（线程安全，全局复用）
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    /**
     * RSA算法完整名称（指定模式和填充方式，避免依赖默认值导致的跨环境问题）
     * ECB模式：对称加密的基础模式（RSA非对称加密常用）
     * PKCS1Padding：填充方式，解决数据长度不足问题
     */
    private static final String RSA_FULL_ALGORITHM = "RSA/ECB/PKCS1Padding";

    /**
     * RSA算法名称（用于密钥生成）
     */
    private static final String RSA_ALGORITHM = "RSA";

    /**
     * 最小密钥长度（安全规范要求，2048位及以上）
     */
    private static final int MIN_KEY_SIZE = 2048;


    /**
     * 将RSA公钥/私钥转换为Base64编码字符串（标准格式，便于存储和传输）
     * <p>
     * 说明：密钥的{@link Key#getEncoded()}方法返回其ASN.1编码格式的字节数组，
     * </p>
     *
     * @param key 公钥{@link PublicKey}或私钥{@link PrivateKey}
     * @return 密钥字符串
     */
    public static <T extends Key> String keyToStr(T key) {
        if (key == null) {
            throw new IllegalArgumentException("密钥不能为空");
        }
        return Base64.encode(key.getEncoded());
    }


    /**
     * 将Base64编码的公钥字符串还原为{@link PublicKey}对象
     *
     * @param publicKey 公钥字符串（通过{@link #keyToStr(Key)} (Key)}生成）
     * @return 还原后的RSA公钥
     * @throws InvalidKeySpecException  当公钥格式无效（非X.509格式）时抛出
     */
    public static PublicKey restorePublicKey(String publicKey) throws InvalidKeySpecException {
        if (publicKey == null || publicKey.isEmpty()) {
            throw new IllegalArgumentException("公钥字符串不能为空");
        }
        X509EncodedKeySpec keySpec;
        PublicKey key;
        try {
            // 1. Base64解码得到原始字节数组（修正原逻辑：直接用字符串getBytes()的错误）
            byte[] keyBytes = Base64.decodeToBytes(publicKey);
            // 2. 用X.509规范解析公钥（RSA公钥标准格式）1-0
            keySpec = new X509EncodedKeySpec(keyBytes);
            key = KeyFactory.getInstance(RSA_ALGORITHM).generatePublic(keySpec);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalArgumentException("Failed to restore public key from string: " + publicKey, e);
        }
        return key;
    }


    /**
     * 将Base64编码的私钥字符串还原为{@link PrivateKey}对象
     *
     * @param privateKey 编码的私钥字符串（通过{@link #keyToStr(Key)} (Key)}生成）
     * @return 还原后的RSA私钥
     * @throws NoSuchAlgorithmException 当JVM不支持RSA算法时抛出
     * @throws InvalidKeySpecException  当私钥格式无效（如Base64解码错误、非PKCS#8格式）时抛出
     */
    public static PrivateKey restorePrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        if (privateKey == null || privateKey.isEmpty()) {
            throw new IllegalArgumentException("私钥字符串不能为空");
        }
        // 1. Base64解码得到原始字节数组
        byte[] keyBytes = Base64.decodeToBytes(privateKey);
        // 2. 用PKCS#8规范解析私钥（RSA私钥标准格式）
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        return keyFactory.generatePrivate(keySpec);
    }


    /**
     * 使用RSA公钥加密数据（公钥加密，私钥解密）
     * <p>
     * 注意：RSA加密有长度限制，最大加密长度 = 密钥长度(字节) - 42（PKCS1Padding填充占用）
     * 例如：2048位密钥（256字节）最大可加密 256 - 42 = 214字节数据，超过需分片加密
     * </p>
     *
     * @param data      待加密的原始数据（字符串）
     * @param publicKey RSA公钥
     * @return 加密后的数据（Base64编码字符串，便于传输）
     * @throws NoSuchAlgorithmException  当JVM不支持RSA算法时抛出
     * @throws NoSuchPaddingException    当指定的填充方式不支持时抛出
     * @throws InvalidKeyException       当公钥无效（如格式错误）时抛出
     * @throws IllegalBlockSizeException 当数据长度超过最大限制时抛出
     * @throws BadPaddingException       当加密过程中填充错误时抛出
     */
    public static String encryptByPublicKey(String data, PublicKey publicKey) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        if (data == null || data.isEmpty()) {
            throw new IllegalArgumentException("待加密数据不能为空");
        }
        if (publicKey == null) {
            throw new IllegalArgumentException("公钥不能为空");
        }

        Cipher cipher = Cipher.getInstance(RSA_FULL_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] encryptedData = cipher.doFinal(data.getBytes());
        // 加密后的数据转为Base64字符串（避免二进制数据传输问题）
        return Base64.encode(encryptedData);
    }


    /**
     * 使用RSA私钥解密数据（对应公钥加密的数据）
     *
     * @param encryptedData 加密后的数据
     * @param privateKey          RSA私钥
     * @return 解密后的原始数据
     * @throws NoSuchAlgorithmException  当JVM不支持RSA算法时抛出
     * @throws NoSuchPaddingException    当指定的填充方式不支持时抛出
     * @throws InvalidKeyException       当私钥无效（如格式错误）时抛出
     * @throws IllegalBlockSizeException 当加密数据格式错误时抛出
     * @throws BadPaddingException       当解密过程中填充验证失败（如数据被篡改）时抛出
     */
    public static String decryptByPrivateKey(String encryptedData, PrivateKey privateKey) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        if (encryptedData == null || encryptedData.isEmpty()) {
            throw new IllegalArgumentException("待解密数据不能为空");
        }
        if (privateKey == null) {
            throw new IllegalArgumentException("私钥不能为空");
        }

        // 1. Base64解码得到加密后的字节数组
        byte[] encryptedDataBytes = Base64.decodeToBytes(encryptedData) ;
        // 2. 解密
        Cipher cipher = Cipher.getInstance(RSA_FULL_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] decryptedData = cipher.doFinal(encryptedDataBytes);
        return new String(decryptedData);
    }


    /**
     * 使用RSA私钥加密数据（私钥加密，公钥解密，通常用于签名场景）
     * <p>
     * 注意：同公钥加密，存在数据长度限制，超过需分片
     * </p>
     *
     * @param data       待加密的原始数据
     * @param privateKey RSA私钥
     * @return 加密后的数据
     * @throws NoSuchAlgorithmException  当JVM不支持RSA算法时抛出
     * @throws NoSuchPaddingException    当指定的填充方式不支持时抛出
     * @throws InvalidKeyException       当私钥无效时抛出
     * @throws IllegalBlockSizeException 当数据长度超过最大限制时抛出
     * @throws BadPaddingException       当加密过程中填充错误时抛出
     */
    public static String encryptByPrivateKey(String data, PrivateKey privateKey) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        if (data == null || data.isEmpty()) {
            throw new IllegalArgumentException("待加密数据不能为空");
        }
        if (privateKey == null) {
            throw new IllegalArgumentException("私钥不能为空");
        }

        Cipher cipher = Cipher.getInstance(RSA_FULL_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        byte[] encryptedData = cipher.doFinal(data.getBytes());
        return Base64.encode(encryptedData);
    }


    /**
     * 使用RSA公钥解密数据（对应私钥加密的数据）
     *
     * @param encryptedData 加密后的数据
     * @param publicKey           RSA公钥
     * @return 解密后的原始数据
     * @throws NoSuchAlgorithmException  当JVM不支持RSA算法时抛出
     * @throws NoSuchPaddingException    当指定的填充方式不支持时抛出
     * @throws InvalidKeyException       当公钥无效时抛出
     * @throws IllegalBlockSizeException 当加密数据格式错误时抛出
     * @throws BadPaddingException       当解密过程中填充验证失败时抛出
     */
    public static String decryptByPublicKey(String encryptedData, PublicKey publicKey) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        if (encryptedData == null || encryptedData.isEmpty()) {
            throw new IllegalArgumentException("待解密数据不能为空");
        }
        if (publicKey == null) {
            throw new IllegalArgumentException("公钥不能为空");
        }

        byte[] encryptedDataBytes = Base64.decodeToBytes(encryptedData);
        Cipher cipher = Cipher.getInstance(RSA_FULL_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, publicKey);
        byte[] decryptedData = cipher.doFinal(encryptedDataBytes);
        return new String(decryptedData);
    }


    /**
     * 生成RSA密钥对（默认2048位，符合安全规范）
     *
     * @return 包含公钥和私钥的{@link KeyPair}
     * @throws NoSuchAlgorithmException 当JVM不支持RSA算法时抛出
     */
    public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
        return generateKeyPair(MIN_KEY_SIZE);
    }


    /**
     * 生成指定长度的RSA密钥对（长度必须≥2048位）
     *
     * @param keySize 密钥长度（单位：位），建议2048/4096
     * @return 包含公钥和私钥的{@link KeyPair}
     * @throws NoSuchAlgorithmException 当JVM不支持RSA算法时抛出
     * @throws IllegalArgumentException 当密钥长度小于2048时抛出
     */
    public static KeyPair generateKeyPair(int keySize) throws NoSuchAlgorithmException {
        if (keySize < MIN_KEY_SIZE) {
            throw new IllegalArgumentException("密钥长度不能小于" + MIN_KEY_SIZE + "位（安全规范要求）");
        }
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
        keyPairGenerator.initialize(keySize);
        return keyPairGenerator.generateKeyPair();
    }

    /**
     * 生成适用于RS256签名的RSA密钥对（2048位）
     * RS256是使用SHA-56的RSA签名算法，需要至少2048位的密钥长度以确保安全性
     *
     * @return 包含公钥和私钥的{@link KeyPair}
     * @throws NoSuchAlgorithmException 当JVM不支持RSA算法时抛出
     */
    public static KeyPair generateRS256KeyPair() throws NoSuchAlgorithmException {
        return generateKeyPair(2048);
    }

    /**
     * 生成适用于RS256签名的RSA密钥对（指定密钥长度）
     *
     * @param keySize 密钥长度（单位：位），建议2048/4096
     * @return 包含公钥和私钥的{@link KeyPair}
     * @throws NoSuchAlgorithmException 当JVM不支持RSA算法时抛出
     */
    public static KeyPair generateRS256KeyPair(int keySize) throws NoSuchAlgorithmException {
        return generateKeyPair(keySize);
    }

    /**
     * 将{@link Key}转换为byte[]表示
     * @param jsonStr 密钥对象
     * @return 密钥字符串表示
     */
    private static  byte[] stringArrToByteArr(String jsonStr) {
        try {
            // 1. 解析JSON字符串为Integer列表
            List<Integer> intList = OBJECT_MAPPER.readValue(
                    jsonStr,
                    new TypeReference<List<Integer>>() {
                    } // 泛型类型引用
            );

            // 2. 转换为byte数组（校验范围）
            byte[] result = new byte[intList.size()];
            for (int i = 0; i < intList.size(); i++) {
                Integer intValue = intList.get(i);
                if (intValue == null) {
                    throw new IllegalArgumentException("数组中存在null元素");
                }
                if (intValue < Byte.MIN_VALUE || intValue > Byte.MAX_VALUE) {
                    throw new IllegalArgumentException(
                            "整数 " + intValue + " 超出 byte 范围（-128 ~ 127）"
                    );
                }
                result[i] = intValue.byteValue(); // 安全转换为byte
            }

            return result;
        } catch (Exception e) {
            throw new IllegalArgumentException(
                    "JSON字符串解析失败：" + e.getMessage(), e
            );
        }
    }
}